/****************************************************************************************************
 *
 * PersonManager.java
 * This class manages persons. 
 * 
 ****************************************************************************************************/
package ict.kth.se.manager;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;

import ict.kth.se.model.repository.AvailabilityRepository;
import ict.kth.se.model.repository.CompetenceProfileRepository;
import ict.kth.se.model.repository.CompetenceRepository;
import ict.kth.se.model.repository.PersonRepository;
import ict.kth.se.model.repository.RoleRepository;
import ict.kth.se.model.Availability;
import ict.kth.se.model.Competence;
import ict.kth.se.model.CompetenceProfile;
import ict.kth.se.model.Person;
import ict.kth.se.model.Role;
import ict.kth.se.web.HomeController;

public class PersonManager {

	private PersonRepository personRepository;
	private RoleRepository roleRepository;
	private CompetenceRepository competenceRepository;
	private AvailabilityRepository availabilityRepository;
	private CompetenceProfileRepository competenceProfileRepository;
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	/**
	 * Empty constructor. We have to use empty and to set variables with setters 
	 * because Spring's AOP needs to be so so that transactional management will work
	 */
	public PersonManager(){}
	
	/**
	 * This method gets persons by id
	 * @param Person's id
	 * @return Found person or null if not found
	 * @throws Exception
	 */
	public Person getPersonById(Long personId) throws Exception{
		try{
			Person person = this.personRepository.getPersonById(personId);
			person.setCompetenceProfiles(this.competenceProfileRepository.getCompetenceProfilesByPersonId(personId));
			person.setAvailabilities(this.availabilityRepository.getAvailabilitiesByPersonId(personId));
			logger.info("Person with id: "+personId+" retrieved.");
			return person;
		}
		catch(Exception e){
			e.printStackTrace();
			logger.error("Error while searching after person by id: "+personId, e);
			throw new Exception("Some internal error showed up, it couldn't search after person by id.");
		}
	}
	
	/**
	 * This method gets all persons that have ROLE_RECRUITER roles
	 * @return List of all recruiters
	 * @throws Exception
	 */
	public List<Person> getAllRecruiters() throws Exception{
		try{
			List<Person> allPersons = new ArrayList<Person>();
			for(Person person : this.personRepository.getAllPersons()){
				if(person.getRole().getName().equals(Role.ROLE_RECRUITER)){
					allPersons.add(person);
				}
			}
			logger.info("All recruiters retrieved.");
			return allPersons;
		}
		catch(Exception e){
			e.printStackTrace();
			logger.error("Couldn't remove person from DB", e);
			throw new Exception("Some internal error showed up, person couldn't be removed.");
		}
	}
	
	/**
	 * This transactional method removes one person from DB
	 * @param person Person to be removed
	 * @throws Exception
	 */
	@Transactional(rollbackFor=Throwable.class)
	public void removePerson(Person person) throws Exception{
		try{
			//Many controlls before addition
			if(person == null)
				throw new Exception("Person can't be null");
			if(person.getFirstName() == null || person.getLastName() == null)
				throw new Exception("Corrupted person's name.");
			
			if(person.getRole() == null)
				throw new Exception("Role can't be null");
			if(person.getRole().getName() == null)
				throw new Exception("Role's name can't be null");
			
			if(person.getAvailabilities() == null)
				throw new Exception("There were no availabilities");
			if(person.getCompetenceProfiles() == null)
				throw new Exception("Competence profiles can't be null");
			
			//Check that for every competence profile there exist competence with that ID
			for(CompetenceProfile competenceProfile : person.getCompetenceProfiles()){
				if(competenceProfile.getCompetence() == null || competenceProfile.getCompetence().getId() == null)
					throw new Exception("Corrupted competence");
				
				if(this.competenceRepository.getCompetenceById(competenceProfile.getCompetence().getId()) == null)
					throw new Exception("There is no such preadded competence with id: "+
							competenceProfile.getCompetence().getId());
			}
			
			//Removes CompetenceProfiles for this person
			this.competenceProfileRepository.removeCompetenceProfilesForPerson(person.getId());
			
			//Remove Availability for this person
			this.availabilityRepository.removeAvailabilitiesForPerson(person.getId());
			
			//Removes person from DB, person gets new ID here
			this.personRepository.removePerson(person);
			
			logger.info("Person with id: "+person.getId()+" and name: "+person.getFirstName()+" "+person.getLastName()+" retrieved.");
		}
		catch(Exception e){
			e.printStackTrace();
			logger.error("Couldn't remove person from DB", e);
			throw new Exception("Some internal error showed up, person couldn't be removed.");
		}
	}
	
	/**
	 * This transactional method removes one person from DB
	 * @param person Person to be removed
	 * @throws Exception
	 */
	@Transactional(rollbackFor=Throwable.class)
	public void removePersonById(Long personid) throws Exception{
		try{
			Person person = this.personRepository.getPersonById(personid);
			//Many controlls before addition
			if(person == null)
				throw new Exception("No such person in DB");
			
			//Removes CompetenceProfiles for this person
			this.competenceProfileRepository.removeCompetenceProfilesForPerson(person.getId());
			
			//Remove Availability for this person
			this.availabilityRepository.removeAvailabilitiesForPerson(person.getId());
			
			//Removes person from DB, person gets new ID here
			this.personRepository.removePerson(person);
			
			logger.info("Person with id: "+person.getId()+" retrieved.");
		}
		catch(Exception e){
			e.printStackTrace();
			logger.error("Couldn't remove person from DB", e);
			throw new Exception("Some internal error showed up, person couldn't be removed.");
		}
	}
	
	/**
	 * This transactional method creates new user (Admin or Recruiter) in DB
	 * @param person Person to be created
	 * @return Newly created person
	 * @throws Exception
	 */
	@Transactional(rollbackFor=Throwable.class)
	public Person createNewUser(Person person) throws Exception{
		try{
			//Many controlls before addition
			if(person == null)
				throw new Exception("Person can't be null");
			if(person.getFirstName() == null || person.getLastName() == null ||
					person.getUsername() == null || person.getPassword() == null)
				throw new Exception("Corrupted person's parameters.");
			
			if(person.getRole() == null)
				throw new Exception("Role can't be null");
			if(person.getRole().getName() == null)
				throw new Exception("Role's name can't be null");
			
			//Get role, check it and add it to person
			Role role = this.roleRepository.getRoleByName(person.getRole().getName());
			if(role == null)
				throw new Exception("No such role in DB.");
			person.setRole(role);
			
			//Adds person into DB, person gets new ID here
			person = this.personRepository.createPerson(person);
			
			logger.info("Person with name: "+person.getFirstName()+" "+person.getLastName()+" retrieved.");
			return person;
			
		}
		catch(Exception e){
			e.printStackTrace();
			logger.error("Couldn't add person to DB", e);
			throw new Exception("Some internal error showed up, person couldn't be created.");
		}
	}
	
	/**
	 * This method sets person repository
	 * @param personRepository
	 */
	public void setPersonRepository(PersonRepository personRepository){
		this.personRepository = personRepository;
	}
	
	/**
	 * This method sets competence repository
	 * @param competenceRepository
	 */
	public void setCompetenceRepository(CompetenceRepository competenceRepository){
		this.competenceRepository = competenceRepository;
	}
	
	/**
	 * This method sets role repository
	 * @param roleRepository
	 */
	public void setRoleRepository(RoleRepository roleRepository){
		this.roleRepository = roleRepository;
	}
			
	/**
	 * This method sets availability repository
	 * @param availabilityRepository
	 */
	public void setAvailabilityRepository(AvailabilityRepository availabilityRepository){
		this.availabilityRepository = availabilityRepository;
	}
	
	/**
	 * This method sets competenceProfile repository
	 * @param competenceProfileRepository
	 */
	public void setCompetenceProfileRepository(CompetenceProfileRepository competenceProfileRepository){
		this.competenceProfileRepository = competenceProfileRepository;
	}
}
